package com.gloaba.common.mongo.service;

import cn.hutool.core.collection.CollectionUtil;
import com.gloaba.common.core.utils.StringUtils;
import com.gloaba.common.mongo.domain.CommonConditionItem;
import com.mongodb.bulk.BulkWriteResult;
import com.mongodb.client.model.Sorts;
import com.mongodb.client.result.UpdateResult;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.collections.MapUtils;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.BulkOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class BaseMongoDocumentService implements IBaseMongoDocumentService {

    @Autowired
    protected MongoTemplate mongoTemplate; // 注入MongoTemplate实例


    public void save(String tableName, String json) {
        mongoTemplate.save(Document.parse(json), tableName);
    }


    public List<Document> findByCriteria(String tableName, Map<String, Object> criteria) {
        Query query = new Query();
        for (Map.Entry<String, Object> entry : criteria.entrySet()) {
            query.addCriteria(Criteria.where(entry.getKey()).is(entry.getValue()));
        }
        return mongoTemplate.find(query, Document.class, tableName);
    }


    public long saveBatch(String tableName, String bsonArrayString) {
        BsonArray bsonArray = BsonArray.parse(bsonArrayString);
        List<Document> documents = new ArrayList<>();
        for (int i = 0; i < bsonArray.size(); i++) {
            BsonDocument bsonDocument = bsonArray.get(i).asDocument();
            Document doc = Document.parse(bsonDocument.toJson());
            //默认增加同步时间、更新状态(主要是给)
            documents.add(doc);
        }
        mongoTemplate.insert(documents, tableName);
        return bsonArray.size();
    }

    public long updateFieldsByConditions(String tableName, Map<String, Object> conditions,Map<String, Object> updateFields) {
        Query query = new Query();
        if(MapUtils.isNotEmpty( conditions)){
            conditions.forEach((key,value)-> query.addCriteria(Criteria.where(key).is(value)));
        }

        Update update = new Update();
        if(MapUtils.isNotEmpty( updateFields)) {
            updateFields.forEach((key, value) -> update.set(key, value));
        }

        UpdateResult result = mongoTemplate.updateFirst(query, update,tableName);
        if(result!=null) {
            return result.getMatchedCount();
        } else {
            return 0;
        }

    }


    public long bulkWrite(String tableName, String pkField, String bsonArrayString) {
        BsonArray bsonArray = BsonArray.parse(bsonArrayString);
        if (bsonArray.size() == 0)
            return 0;
        BulkOperations operations = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, tableName);
        List<Pair<Query, Update>> updateList = new ArrayList<>();
        for (int i = 0; i < bsonArray.size(); i++) {
            BsonDocument bsonDocument = bsonArray.get(i).asDocument();
            Query query = new Query(Criteria.where(pkField).is(bsonDocument.get(pkField)));
            Update update = new Update();
            for (String filed : bsonDocument.keySet()) {
                update.set(filed, bsonDocument.get(filed));
                update.set("SyncTimeStamp", System.currentTimeMillis());
            }
            Pair<Query, Update> updatePair = Pair.of(query, update);
            updateList.add(updatePair);
        }

        operations.upsert(updateList);
        BulkWriteResult result = operations.execute();
        return bsonArray.size();
    }

    public BulkWriteResult bulkWrite(String tableName, String pkField, List<Map<String, Object>> mapList) {
        if (mapList.size() == 0)
            return null;
        BulkOperations operations = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, tableName);
        List<Pair<Query, Update>> updateList = new ArrayList<>();
        for (Map<String, Object> map : mapList) {
            Query query = new Query(Criteria.where(pkField).is(map.get(pkField)));
            Update update = new Update();
            for (String filed : map.keySet()) {
                update.set(filed, map.get(filed));
                update.set("SyncTimeStamp", System.currentTimeMillis());
            }
            Pair<Query, Update> updatePair = Pair.of(query, update);
            updateList.add(updatePair);
        }

        operations.upsert(updateList);
        BulkWriteResult result = operations.execute();
        return result;
    }


    /**
     * 删除集合
     *
     * @param tableName
     */
    public void dropCollection(String tableName) {
        mongoTemplate.dropCollection(tableName);
    }

    /**
     * 根据条件计数
     *
     * @param tableName  表名
     * @param conditions 条件
     * @return 匹配条件的文档数量
     */
    public long countByConditions(String tableName, Map<String, Object> conditions) {
        Query query = new Query();
        conditions.forEach((key, value) -> {
            query.addCriteria(Criteria.where(key).is(value));
        });
        return mongoTemplate.count(query, tableName);
    }

    /**
     * 聚合集合
     */
    public long countDocuments(String tableName) {
        return mongoTemplate.count(new Query(), tableName);
    }

    /**
     * 条件聚合
     */
    public long countDocumentsByCondition(String tableName, String key, String value) {
        Criteria criteria = Criteria.where(key).is(value);
        Query query = new Query(criteria);
        return mongoTemplate.count(query, tableName);
    }


    /**
     * 条件聚合重写
     *
     * @return
     */
    public long countDocumentsByConditions(List<Map<String, String>> conditionLists, String tableName) {
        Map<String, Object> conditions = new HashMap<>();
        for (Map<String, String> conditionMap : conditionLists) {
            conditions.put(conditionMap.get("key"), conditionMap.get("value"));
        }
        return countDocumentsByConditions(tableName, conditions);
    }

    /**
     * 条件聚合重写
     *
     * @return
     */
    public long countDocumentsByConditions(String tableName, Map<String, Object> conditions) {
        Criteria criteria = new Criteria();
        conditions.forEach((key, value) -> {
            // 这里假设所有条件都是“等于”，如果需要其他比较操作，需要进行相应的逻辑处理
            criteria.and(key).is(value);
        });
        Query query = new Query(criteria);
        return mongoTemplate.count(query, tableName);
    }


    /**
     * 检查集合（表）是否存在
     *
     * @param tableName 集合的名称
     * @return 存在返回true，不存在返回false
     */
    public boolean collectionExists(String tableName) {
        return mongoTemplate.collectionExists(tableName);
    }


    /**
     * 分页查询
     *
     * @param tableName 表名
     * @param page      页码
     * @param size      每页大小
     * @param sortBy    排序字段
     * @param isAsc     是否升序
     * @return 分页结果
     */
    public List<Document> findByPage(String tableName, int page, int size, String sortBy, boolean isAsc) {
        PageRequest pageable = PageRequest.of(page, size, (Sort) (isAsc ? Sorts.ascending(sortBy) : Sorts.descending(sortBy)));
        Query query = new Query().with(pageable);
        return mongoTemplate.find(query, Document.class, tableName);
    }


    /**
     * 条件分页查询
     *
     * @param tableName  表名
     * @param page       页码
     * @param size       每页大小
     * @param sortBy     排序字段
     * @param isAsc      是否升序
     * @param conditions 条件Map
     * @return 分页结果
     */
    public Page<Document> findByPage(String tableName, int page, int size, String sortBy, boolean isAsc, Map<String, Object> conditions) {
        Query query = new Query();
        if (ObjectUtils.isNotEmpty(conditions.get("beginDate"))) {
            // 添加大于 startDate 的条件
            Criteria criteriaGreaterThan = Criteria.where("report_date").gt(conditions.get("beginDate"));
            query.addCriteria(criteriaGreaterThan);
        }
        if (ObjectUtils.isNotEmpty(conditions.get("endDate"))) {
            // 添加大于 startDate 的条件
            Criteria criteriaGreaterThan = Criteria.where("report_date").lt(conditions.get("endDate"));
            query.addCriteria(criteriaGreaterThan);
        }
        Set<String> keys = conditions.keySet();
        for (String key : keys) {
            Criteria criteria = Criteria.where(key).is(conditions.get("key"));
            query.addCriteria(criteria);
        }
        long count = mongoTemplate.count(query, tableName);
        Sort sort = isAsc ? Sort.by(Sort.Direction.ASC, sortBy) : Sort.by(Sort.Direction.DESC, sortBy);
        PageRequest pageable = PageRequest.of(page, size, sort);
        query.fields();
        query.with(pageable);
        return new PageImpl<>(mongoTemplate.find(query, Document.class, tableName), pageable, count);
    }


    /**
     * 根据条件查询第一个文档的指定字段
     *
     * @param tableName  表名
     * @param field      返回的字段
     * @param conditions 条件
     * @param sortBy     排序字段
     * @param isAsc      排序方式
     * @return 第一个匹配文档的指定字段值
     */
    public Object findFirstByConditions(String tableName, String field, Map<String, Object> conditions, String sortBy, boolean isAsc) {
        Query query = new Query();
        conditions.forEach((key, value) -> {
            query.addCriteria(Criteria.where(key).is(value));
        });

        Sort sort = isAsc ? Sort.by(Sort.Direction.ASC, sortBy) : Sort.by(Sort.Direction.DESC, sortBy);
        query.with(sort);

        ProjectionOperation projection = Aggregation.project(field);
        Aggregation aggregation = Aggregation.newAggregation(projection);

        AggregationResults<Document> results = mongoTemplate.aggregate(aggregation, tableName, Document.class);
        List<Document> list = results.getMappedResults();

        return list.isEmpty() ? null : list.get(0).get(field);
    }

    public Page<Document> findByPage(String tableName, int page, int size, String sortBy, boolean isAsc, List<CommonConditionItem> params,String includFields,String excludeFields) {

        return doFindByPage(tableName, page, size, sortBy, isAsc, params, StringUtils.splitList(includFields),StringUtils.splitList(excludeFields));
    }

    public Page<Document> findByPage(String tableName, int page, int size, String sortBy, boolean isAsc, List<CommonConditionItem> params) {
        return doFindByPage(tableName, page, size, sortBy, isAsc, params,null,null);
    }

    public Page<Document> doFindByPage(String tableName, int page, int size, String sortBy, boolean isAsc, List<CommonConditionItem> params,List<String> includFields,List<String> excludeFields) {
        Query query = new Query();
        Criteria criteria = new Criteria();
        if(CollectionUtil.isNotEmpty(params)){
            for (CommonConditionItem condition : params) {
                addCriteria(criteria, condition);
            }
            query.addCriteria(criteria);
        }
        //输出指定的字段
        if(CollectionUtil.isNotEmpty(includFields)){
            query.fields().include(includFields.stream().toArray(String[]::new));
        } else if(CollectionUtil.isNotEmpty(excludeFields)){
            //排除字段
            query.fields().exclude(excludeFields.stream().toArray(String[]::new));
        }
        else {
            query.fields();
        }


        long count = mongoTemplate.count(query, tableName);
        Sort sort = isAsc ? Sort.by(Sort.Direction.ASC, sortBy) : Sort.by(Sort.Direction.DESC, sortBy);
        PageRequest pageable = PageRequest.of(page, size, sort);

        query.with(pageable);
        return new PageImpl<>(mongoTemplate.find(query, Document.class, tableName), pageable, count);
    }


    private void addCriteria(Criteria criteria, CommonConditionItem condition) {

        switch (condition.getOperateType()) {
            case EQ :
                criteria.and(condition.getField()).is(condition.getValue());
                break;
            case NE :
                criteria.and(condition.getField()).ne(condition.getValue());
                break;
            case GT :
                criteria.and(condition.getField()).gt(condition.getValue());
                break;
            case LT :
                criteria.and(condition.getField()).lt(condition.getValue());
                break;
            case REGEX :
                criteria.and(condition.getField()).regex(condition.getValue().toString());
                break;
            case GTE :
                criteria.and(condition.getField()).gte(condition.getValue());
                break;
            case LTE :
                criteria.and(condition.getField()).lte(condition.getValue());
                break;
            case IN :
                criteria.and(condition.getField()).in(condition.getValue());
                break;
            case BETWEEN :
            {
                List<Object> list = (List<Object>) condition.getValue();
                criteria.and(condition.getField()).gte(list.get(0)).lte(list.get(1));
                break;
            }
        }

    }


}
